package com.skycity.auth.config;

import cn.skycity.framework.core.constants.GlobalConstants;
import com.skycity.auth.authentication.convert.OAuth2PasswordAuthenticationConverter;
import com.skycity.auth.authentication.provider.OAuth2PasswordAuthenticationProvider;
import com.skycity.auth.security.userdetails.SysUserDetails;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

/**
 * @author: ReLive
 * @date: 2022/05/22 22:30 下午
 */
@Configuration
@Slf4j
//@EnableWebSecurity
public class AuthorizationServerConfig {

    @Resource
    private UserDetailsService users;
    @Resource
    private PasswordEncoder passwordEncoder;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        OAuth2PasswordAuthenticationProvider oAuth2ResourcePasswordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(users, passwordEncoder);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .tokenEndpoint(tokenEndpoint ->{
                    tokenEndpoint.authenticationProviders(authenticationProviders -> {
                        authenticationProviders.add(0,oAuth2ResourcePasswordAuthenticationProvider);
                    }).accessTokenRequestConverters(authenticationConverters -> {
                        authenticationConverters.add(0,new OAuth2PasswordAuthenticationConverter());
                    });
                })
                .authorizationEndpoint(endPoint ->
                        endPoint.consentPage("/oauth2/consent"));
        http.exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                .sessionManagement(AbstractHttpConfigurer::disable)
                .anonymous(AbstractHttpConfigurer::disable)
                .headers(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .oauth2ResourceServer(AbstractHttpConfigurer::disable)
        ;
        http.setSharedObject(OAuth2TokenCustomizer.class, jwtTokenCustomizer());
        DefaultSecurityFilterChain build = http.build();
        OAuth2TokenGenerator oAuth2TokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);
        OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
        // 以上三个bean在build()方法之后调用是因为调用build方法时框架会尝试获取这些类，
        // 如果获取不到则初始化一个实例放入SharedObject中，所以要在build方法调用之后获取
        // 在通过set方法设置进provider中，但是如果在build方法之后调用authenticationProvider(provider)
        // 框架会提示unsupported_grant_type，因为已经初始化完了，在添加就不会生效了
        oAuth2ResourcePasswordAuthenticationProvider.setOAuth2TokenGenerator(oAuth2TokenGenerator);
        oAuth2ResourcePasswordAuthenticationProvider.setAuthorizationService(authorizationService);
        return build;
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

//    @Bean
//    public RegisteredClientRepository registeredClientRepository() {
//        RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
//                .clientId("oidc-client")
//                .clientSecret("{noop}secret")
//                .clientName("oidc-client")
//                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
//                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
//                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
//                .authorizationGrantType(SecurityConstants.PASSWORD)
//                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
//                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
//                .redirectUri("http://127.0.0.1:9999/login/oauth2/code/oidc-client")
//                .postLogoutRedirectUri("http://127.0.0.1:8080/")
//                .scope(OidcScopes.PROFILE)
//                .scope(OidcScopes.EMAIL)
//                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
//                .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
//                        .accessTokenTimeToLive(Duration.ofDays(7))
//                        .refreshTokenTimeToLive(Duration.ofDays(30))
//                        .reuseRefreshTokens(false).build())
//                .build();
//
//        return new InMemoryRegisteredClientRepository(oidcClient);
//    }

    @Bean
    public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> jwtTokenCustomizer() {
        return (context) -> {
            if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
                context.getClaims().claims((claims) -> {
                    Object details = context.getPrincipal().getDetails();
                    if(details instanceof SysUserDetails){
                        claims.put(GlobalConstants.USER_ID,((SysUserDetails) details).getUserId());
                        claims.put(GlobalConstants.USER_NAME,((SysUserDetails) details).getUsername());
                    }
                });
            }
        };
    }
}
